package com.onpositive.data.units;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

public class DateWithAccuracy implements Comparable<DateWithAccuracy>, Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public static final byte ACCURACY_MILLISECOND = (byte)0 ;
	public static final byte ACCURACY_SECOND	  = (byte)1 ;
	public static final byte ACCURACY_MINUTE	  = (byte)2 ;
	public static final byte ACCURACY_HOUR 	  	  = (byte)3 ;
	public static final byte ACCURACY_DAY		  = (byte)4 ;
	public static final byte ACCURACY_MONTH		  = (byte)5 ;
	public static final byte ACCURACY_YEAR	 	  = (byte)6 ;
	
	private static final int[] fieldsArray = {
		GregorianCalendar.MILLISECOND ,
		GregorianCalendar.SECOND,
		GregorianCalendar.MINUTE,
		GregorianCalendar.HOUR_OF_DAY,
		GregorianCalendar.DAY_OF_MONTH,
		GregorianCalendar.MONTH,
		GregorianCalendar.YEAR,
	};
	
	private static final int fieldsCount = fieldsArray.length ;
	
	Date date;
	private byte accuracy ;
	private int code;
	
	public GregorianCalendar getCalendar(){
		return getCl(code);
	}
	
	public DateWithAccuracy( Date date, byte accuracy) {
		super();
		this.date=date;
		if (accuracy < 0) //XXX
			accuracy = 0;
		this.accuracy = accuracy;
		GregorianCalendar cl = new GregorianCalendar();
		cl.setTime(date);
		this.code=toDateCode(cl);
	}
	
	public DateWithAccuracy( int dateCode ) {
		super();
		this.code=dateCode;
		GregorianCalendar cl = getCl(dateCode);
		date=cl.getTime();
	}

	private GregorianCalendar getCl(int dateCode) {
		boolean is_BC = (dateCode & 1) != 0 ;
		GregorianCalendar cl=new GregorianCalendar();
		if( is_BC )
			cl.set(GregorianCalendar.ERA, GregorianCalendar.BC) ;
		
		dateCode >>= 1 ;
		
		this.accuracy = (byte) (dateCode & ( (1<<8)-1 )) ;	
		if (accuracy < 0)
			accuracy = 0; //XXX
		dateCode >>= 8 ;
		int day = dateCode & ( (1<<9)-1 ) ;
		dateCode >>= 9 ;

		cl.set( GregorianCalendar.YEAR, dateCode ) ;
		
		cl.set( GregorianCalendar.DAY_OF_YEAR, day ) ;
		return cl;
	}
	
	protected int  toDateCode(GregorianCalendar cl){
		int dateCode = cl.get(GregorianCalendar.YEAR) ;
		dateCode <<= 9 ;
		dateCode  += cl.get(GregorianCalendar.DAY_OF_YEAR) ;
		dateCode <<= 8 ;
		dateCode += accuracy ;
		dateCode <<= 1 ;
		if( cl.get( GregorianCalendar.ERA) == GregorianCalendar.BC )
			dateCode+=1 ;
		return dateCode;
	}
	
	public int getDateCode()
	{
		return code;
	}

	public Date getDate() {
		return date;
	}
	

	public byte getAccuracy() {
		return accuracy;
	}
	
	@Override
	public String toString()
	{
		if (code==0){
			return "Never";
		}
		GregorianCalendar cl= getCl(code);
		//String test = DateFormat.getDateInstance( DateFormat.LONG, Locale.US ).format( cl.getTime() ) ;
		StringBuilder builder = new StringBuilder() ;
		if (accuracy < 0)//XXX
			accuracy = 0; 
		for( int i = accuracy ; i < fieldsCount ; i++ )
		{
			String fieldString = cl.getDisplayName( fieldsArray[i], GregorianCalendar.LONG, Locale.US );
			if( fieldString != null )
				builder.append( fieldString ) ;
			else{
				int fieldInt = cl.get( fieldsArray[i] ) ;
				builder.append( fieldInt ) ;
			}			
			builder.append(" ") ;
		}
		
		String result = builder.substring(0, builder.length()-1 ) ;
		return result ;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + accuracy;
		result = prime * result + code;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		DateWithAccuracy other = (DateWithAccuracy) obj;
		if (accuracy != other.accuracy)
			return false;
		
		if (!this.toString().equals(other.toString())){
				return false;//FIXME;
		}		
		return true;
	}

	@Override
	public int compareTo(DateWithAccuracy o)
	{
		if(o==null)
			return 1;
		
		int result = this.getDate().compareTo( o.getDate() ) ;
		
		if( result == 0 )
			result = this.getAccuracy() - o.getAccuracy() ;
		
		return result ;
	}

	
	public static DateWithAccuracy parseSimpleDate(String value)
	{
		return parseSimpleDate(value, Locale.US);
	}
	public static DateWithAccuracy parseSimpleDate(String value,Locale us) {
		
		try {
			DateFormat dateInstance = new SimpleDateFormat("dd,MM,yyyy", us );
			Date parse = dateInstance.parse(value);
			return new DateWithAccuracy(parse, DateWithAccuracy.ACCURACY_DAY );
		} catch (ParseException e) {
			//e.printStackTrace() ;		
		}
		try{
			DateFormat dateInstance = new SimpleDateFormat("dd,MMMM,yyyy", us );
			Date parse = dateInstance.parse(value);
			return new DateWithAccuracy(parse, DateWithAccuracy.ACCURACY_DAY );
		} catch (ParseException e) {
			//e.printStackTrace() ;		
		}
		try{
			DateFormat dateInstance = new SimpleDateFormat("MMMM,dd,yyyy", us );
			Date parse = dateInstance.parse(value);
			return new DateWithAccuracy(parse, DateWithAccuracy.ACCURACY_DAY );
		} catch (ParseException e) {
			//e.printStackTrace() ;		
		}
		try{
			DateFormat dateInstance = new SimpleDateFormat("MMMM,yyyy", us );
			Date parse = dateInstance.parse(value);
			return new DateWithAccuracy(parse, DateWithAccuracy.ACCURACY_MONTH );
		} catch (ParseException e) {
			//e.printStackTrace() ;		
		}
		
		try{
			DateFormat dateInstance = new SimpleDateFormat("yyyy,MMMM", us );
			Date parse = dateInstance.parse(value);
			return new DateWithAccuracy(parse, DateWithAccuracy.ACCURACY_MONTH );
		} catch (ParseException e) {
			//e.printStackTrace() ;		
		}
		
		String value0 = value.replace(",", "") ;
		
		try{
			DateFormat dateInstance = new SimpleDateFormat("ddMMMMyyyy", us );
			Date parse = dateInstance.parse(value0);
			return new DateWithAccuracy(parse, DateWithAccuracy.ACCURACY_DAY );			
		} catch (ParseException e) {
			//e.printStackTrace() ;		
		}
		
		try{
			DateFormat dateInstance = new SimpleDateFormat("MMMMyyyy", us );
			Date parse = dateInstance.parse(value0);
			return new DateWithAccuracy(parse, DateWithAccuracy.ACCURACY_MONTH);
		} catch (ParseException e) {
			//e.printStackTrace() ;		
		}		
		
		if( value.contains(",") )
			return null ;
		try{
			DateFormat dateInstance = new SimpleDateFormat("yyyy", us );
			Date parse = dateInstance.parse(value0);
			return new DateWithAccuracy(parse, DateWithAccuracy.ACCURACY_YEAR);			
		} catch (ParseException e) {
			//e.printStackTrace() ;		
		}
		return null ;
	}

	public boolean isNever() {
		return code==0;
	}
	
}
